/*******************************************************************************
 * Copyright (c) 2007 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
package com.cea.papyrus.extensionpoints.transformation.launch;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.ui.AbstractLaunchConfigurationTab;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.ContainerSelectionDialog;
import org.eclipse.ui.dialogs.ElementTreeSelectionDialog;
import org.eclipse.ui.model.WorkbenchContentProvider;
import org.eclipse.ui.model.WorkbenchLabelProvider;

import com.cea.papyrus.extensionpoints.transformation.definition.ITransformationContext;
import com.cea.papyrus.extensionpoints.transformation.definition.Transformation;
import com.cea.papyrus.extensionpoints.transformation.definition.TransformationContext;
import com.cea.papyrus.extensionpoints.transformation.definition.TransformationExtIds;
import com.cea.papyrus.extensionpoints.transformation.dialog.EditPropertiesDialog;
import com.cea.papyrus.extensionpoints.transformation.dialog.SelectTransformationDialog;
import com.cea.papyrus.extensionpoints.transformation.filter.FileSelectionFilter;
import com.cea.papyrus.extensionpoints.transformation.validator.FileSelectionValidator;
import com.cea.papyrus.extensionpoints.transformation.validator.FolderSelectionValidator;

/**
 * Main Tab used to configure a generation with a module.
 *
 */
public class LaunchTransformationMainTab extends AbstractLaunchConfigurationTab {

	// Key used to store the selected transformation in label data
	private static final String MODULE_ID_KEY = "module_key";

	//module selection
	private Label selectedModuleLabel;
	private Button moduleButton;
	
	//module selection
	private Text selectedModuleDescription;
	
	//internal module configuration
	private Button propertiesButton;
	private Map<Object, String> propertyMap = null;

	//model selection
	private Text modelText;
	private Button modelButton;

	//container selection
	private Text containerText;
	private Button containerButton;

	//log selection
	private Text logText;
	private Button logButton;
	private Button fDefaultLog;

	//sync activation
	private Button fSync;
	
	/*
	 * (non-Javadoc)
	 * @see org.eclipse.debug.ui.ILaunchConfigurationTab#createControl(org.eclipse.swt.widgets.Composite)
	 */
	public void createControl(Composite parent) {
		Composite mainComposite = new Composite(parent, SWT.NONE);
		setControl(mainComposite);
		GridLayout layout = new GridLayout();
		GridData gridData = new GridData(GridData.FILL_HORIZONTAL);
		gridData.horizontalSpan = 2;
		layout.numColumns = 2;
		layout.makeColumnsEqualWidth = false;
		mainComposite.setLayout(layout);
		mainComposite.setLayoutData(gridData);
		mainComposite.setFont(parent.getFont());

		//module selection part
		createModuleComponent(mainComposite);

		//module configuration part
		createConfComponent(mainComposite);
		
		//synchronization activation
		createSyncComponent(mainComposite);
		
		initializeComponents();
	}

	/**
	 * Module selection part
	 * @param parent the parent Composite
	 */
	private void createModuleComponent(Composite parent) {
		Group group = new Group(parent, SWT.NONE);
		group.setText("Module"); 
		GridData gd = new GridData(SWT.FILL, SWT.NONE, true, false);
		gd.horizontalSpan = 2;
		group.setLayoutData(gd);
		GridLayout layout = new GridLayout(3, false);
		group.setLayout(layout);
		group.setFont(parent.getFont());

		selectedModuleLabel = new Label(group, SWT.HORIZONTAL);
		gd = new GridData(SWT.BEGINNING, SWT.NORMAL, true, false);
		selectedModuleLabel.setLayoutData(gd);

		moduleButton = createPushButton(group, "&Browse...", null);
		moduleButton.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				browseModules();
			}
		});

		propertiesButton = createPushButton(group, "&Properties...", null);
		propertiesButton.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				editProperties();
			}
		});
		propertiesButton.setEnabled(false);
		
		// Create textual zone containing module description
		Group descGroup = new Group(group, SWT.NONE);
		descGroup.setText("Description");
		gd = new GridData(SWT.FILL, SWT.NONE, true, true);
		gd.horizontalSpan = 3;
		descGroup.setLayoutData(gd);
		layout = new GridLayout(2, false);
		descGroup.setLayout(layout);
		descGroup.setFont(parent.getFont());
		
		selectedModuleDescription = new Text(descGroup, SWT.MULTI |SWT.WRAP);
		gd = new GridData(SWT.FILL, SWT.NORMAL, true, true);
		gd.horizontalSpan = 2;
		selectedModuleDescription.setLayoutData(gd);
		selectedModuleDescription.setEditable(false); // read only description
		selectedModuleDescription.setBackground(group.getBackground());
	}

	/**
	 * Module configuration part
	 * @param parent the parent Composite
	 */
	private void createConfComponent(Composite parent) {
		Group group = new Group(parent, SWT.NONE);
		group.setText("Configuration"); 
		GridData gd = new GridData(SWT.FILL, SWT.NONE, true, false);
		gd.horizontalSpan = 2;
		group.setLayoutData(gd);
		GridLayout layout = new GridLayout(3, false);
		group.setLayout(layout);
		group.setFont(parent.getFont());

		// model selection sub-part
		createModelSelector(group);
		// container selection sub-part
		createContainerSelector(group);
		// log selection sub-part
		createLogSelector(group);
	}
	
	/**
	 * Create the composite used to (de-)select the synchronization option 
	 * @param parent the parent composite
	 */
	private void createSyncComponent(Composite parent) {
		Group group = new Group(parent, SWT.NONE);
		group.setText("Synchronization"); 
		GridData gd = new GridData(SWT.FILL, SWT.NONE, true, false);
		gd.horizontalSpan = 2;
		group.setLayoutData(gd);
		GridLayout layout = new GridLayout(3, false);
		group.setLayout(layout);
		group.setFont(parent.getFont());
		
		fSync = createCheckButton(group, "activate/desactivate synchronization on next launch");
		gd = new GridData(SWT.BEGINNING, SWT.NORMAL, true, false);
		gd.horizontalSpan = 5;
		fSync.setLayoutData(gd);
		fSync.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				updateLaunchConfigurationDialog();
			}
		});
	}

	/**
	 * Model selection sub-part
	 * @param parent the parent Composite
	 */
	private void createModelSelector(Composite parent) {
		Label modelLabel = new Label(parent, SWT.NONE);
		modelLabel.setText("&Source model :");
		GridData gd = new GridData(GridData.BEGINNING);
		modelLabel.setLayoutData(gd);

		modelText = new Text(parent, SWT.SINGLE | SWT.BORDER);
		gd = new GridData(GridData.FILL_HORIZONTAL);
		modelText.setEditable(false);
		modelText.setLayoutData(gd);
		modelText.addModifyListener(new ModifyListener() {
			public void modifyText(ModifyEvent e) {
				updateDefaultLog();
				updateLaunchConfigurationDialog();
			}
		});

		modelButton = createPushButton(parent, "&Browse...", null);
		modelButton.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				List<String> validFileExtensions = new ArrayList<String>();
				validFileExtensions.add("uml");
				browseFiles("Select the source model for generation", validFileExtensions, modelText);
			}
		});
	}

	/**
	 * Container selection sub-part
	 * @param parent the parent Composite
	 */
	private void createContainerSelector(Composite parent) {
		Label containerLabel = new Label(parent, SWT.NONE);
		containerLabel.setText("&Target folder :");
		GridData gd = new GridData(GridData.BEGINNING);
		containerLabel.setLayoutData(gd);

		containerText = new Text(parent, SWT.SINGLE | SWT.BORDER);
		gd = new GridData(GridData.FILL_HORIZONTAL);
		containerText.setLayoutData(gd);
		containerText.setEditable(false);
		containerText.addModifyListener(new ModifyListener() {
			public void modifyText(ModifyEvent e) {
				updateDefaultLog();
				updateLaunchConfigurationDialog();
			}
		});

		containerButton = createPushButton(parent, "&Browse...", null);
		containerButton.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				browseContainers("Select the target folder for generated files");
			}
		});
	}

	/**
	 * Log selection sub-part
	 * @param parent the parent Composite
	 */
	private void createLogSelector(Composite parent) {
		fDefaultLog = createCheckButton(parent, "use the default log file");
		GridData gd = new GridData(SWT.BEGINNING, SWT.NORMAL, true, false);
		gd.horizontalSpan = 5;
		fDefaultLog.setLayoutData(gd);

		Label containerLabel = new Label(parent, SWT.NONE);
		containerLabel.setText("&Log file:");
		gd = new GridData(GridData.BEGINNING);
		containerLabel.setLayoutData(gd);

		logText = new Text(parent, SWT.SINGLE | SWT.BORDER);
		gd = new GridData(GridData.FILL_HORIZONTAL);
		logText.setLayoutData(gd);
		logText.addModifyListener(new ModifyListener() {
			public void modifyText(ModifyEvent e) {
				updateLaunchConfigurationDialog();
			}
		});

		logButton = createPushButton(parent, "&Browse...", null);
		logButton.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				browseFiles("select the log file", null,logText);
			}
		});

		fDefaultLog.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				boolean enabled = !fDefaultLog.getSelection();
				logText.setEnabled(enabled);
				logButton.setEnabled(enabled);
				updateDefaultLog();
				updateLaunchConfigurationDialog();
			}
		});

		fDefaultLog.setSelection(true);
		logText.setEnabled(false);
		logButton.setEnabled(false);
	}

	/**
	 * Updates the default log if necessary
	 */
	private void updateDefaultLog(){
		if (fDefaultLog.getSelection()){
			String container = containerText.getText().trim();
			String model = modelText.getText().trim();
			if (isModelValid() && isContainerValid()) {
				IPath containerIPath = new Path(container); 
				IResource containerResource = ResourcesPlugin.getWorkspace().getRoot().findMember(containerIPath);
				IProject project = containerResource.getProject();
				String root = "/"+project.getName();
				logText.setText(root+"/"+(new File(model)).getName()+".log.txt");
			}
		}
	}

	/**
	 * Open a resource chooser to select the container.
	 * @param comment message shown in dialog
	 */
	private void browseContainers(String comment) {

		ContainerSelectionDialog dialog= 
			new ContainerSelectionDialog(getShell(), ResourcesPlugin.getWorkspace().getRoot(), false, comment);
		dialog.setValidator(new FolderSelectionValidator());
		dialog.setTitle("Folder selection");
		dialog.setHelpAvailable(false);
		dialog.showClosedProjects(false);

		if (dialog.open() == Window.OK) {
			Object[] elements= dialog.getResult();
			if (elements.length == 1) {
				String file = 
					((IPath)elements[0]).toOSString();
				containerText.setText(file);
			}
		}
	}

	/**
	 * Open a resource chooser to select a module (a transformation).
	 */
	private void browseModules() {
		
		SelectTransformationDialog dialog = new SelectTransformationDialog(
				PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell()
				, "Select the module"
				, TransformationExtIds.TRANSFORMATION_EXTENSION_NAME);
		
		int result = dialog.open();
		if ((result == Window.OK) && (dialog.getFirstResult() != null)){
			
			Object selection = dialog.getFirstResult();
			if (selection instanceof Transformation) {
				Transformation transformation = (Transformation) selection;
				propertiesButton.setEnabled(true);

				if (transformation.getName() != null) {
					selectedModuleLabel.setText(transformation.getName());
				} else {
					selectedModuleLabel.setText(transformation.getId());
				}
				selectedModuleLabel.setData(MODULE_ID_KEY, transformation);
				selectedModuleLabel.pack(true);

				if (transformation.getDescription() != null) {
					selectedModuleDescription.setText(transformation.getDescription());	
				}
				fSync.setEnabled(transformation.isSynchronizable());

				updateLaunchConfigurationDialog();
			}
		}
	}

	/**
	 * Open a resource chooser to select a file.
	 * @param comment the title of the resource chooser
	 * @param filetypes
	 * @param text
	 */
	private void browseFiles(String comment, List<String> filetypes, Text text) {

		ILabelProvider lp= new WorkbenchLabelProvider();
		ITreeContentProvider cp= new WorkbenchContentProvider();
		ElementTreeSelectionDialog dialog= 
			new ElementTreeSelectionDialog(getShell(), lp, cp);

		dialog.setTitle("File selection");
		dialog.setMessage(comment);
		dialog.setInput(ResourcesPlugin.getWorkspace().getRoot());
		dialog.addFilter(new FileSelectionFilter(filetypes));
		dialog.setValidator(new FileSelectionValidator());
		dialog.setDoubleClickSelects(true);
		dialog.setHelpAvailable(false);
		dialog.setAllowMultiple(false);
		
		if (dialog.open() == Window.OK) {
			Object[] elements= dialog.getResult();
			if (elements.length == 1) {
				
				String file = 
					((IFile)elements[0]).getFullPath().toOSString();
				text.setText(file);
			}
		}
	}

	/**
	 * Open a resource chooser to edit module properties.
	 */
	private void editProperties() {
		Transformation tranformation = (Transformation) selectedModuleLabel.getData(MODULE_ID_KEY);
		EditPropertiesDialog dialog = new EditPropertiesDialog(
				PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
				tranformation.configElement,
				propertyMap);
		if (dialog.open() == Window.OK)
			if (dialog.getPropertyMap() != null) {
				propertyMap = dialog.getPropertyMap();
				updateLaunchConfigurationDialog();
			}
	}
	
	/**
	 * GUI component initialization
	 */
	private void initializeComponents(){
		fDefaultLog.setSelection(true);
		logText.setEnabled(false);
		logButton.setEnabled(false);
		propertiesButton.setEnabled(false);
		fSync.setSelection(false);
		fSync.setEnabled(false);
	}

	/*
	 * (non-Javadoc)
	 * @see org.eclipse.debug.ui.ILaunchConfigurationTab#setDefaults(org.eclipse.debug.core.ILaunchConfigurationWorkingCopy)
	 */
	public void setDefaults(ILaunchConfigurationWorkingCopy configuration) {
	}

	/*
	 * (non-Javadoc)
	 * @see org.eclipse.debug.ui.ILaunchConfigurationTab#initializeFrom(org.eclipse.debug.core.ILaunchConfiguration)
	 */
	public void initializeFrom(ILaunchConfiguration configuration) {
		try {
			ITransformationContext context = new TransformationContext(configuration);
			
			// Set log mode
			String mode = configuration.getAttribute(LaunchConfigurationIds.DEFAULT_LOG_BOOL,(String)null);
			if (mode != null) {
				fDefaultLog.setSelection(new Boolean(mode).booleanValue());
				boolean enabled = !fDefaultLog.getSelection();
				logText.setEnabled(enabled);
				logButton.setEnabled(enabled);
				updateDefaultLog();
			}
			
			// Set Container
			IContainer container = context.getContainer();
			if (container != null) {
				containerText.setText(container.getFullPath().toOSString());
			}

			// Set module
			Transformation transformation = context.getTransformation();
			if (transformation != null) {
				String moduleId = transformation.getId();
				if (moduleId != null) {
					selectedModuleLabel.setText(transformation.getName());
					selectedModuleLabel.setData(MODULE_ID_KEY, transformation);
					selectedModuleLabel.pack(true);

					propertiesButton.setEnabled(true);
					if (transformation.getDescription() != null) {
						selectedModuleDescription.setText(transformation.getDescription());	
					}
					fSync.setEnabled(transformation.isSynchronizable());
				}
			}

						
			// Set sync button
			fSync.setSelection(context.isSynchronized());
			
			// Set model
			IFile modelFile = (IFile) context.getSource();
			if (modelFile != null) {
				modelText.setText(modelFile.getFullPath().toOSString());
			}

			// Set log file
			String log = context.getLog();
			if (log != null) {
				logText.setText(log);
			}

			propertyMap = context.getProperties();
			updateLaunchConfigurationDialog();
		
		} catch (CoreException e) {
			setErrorMessage(e.getMessage());
		}
	}

	/*
	 * (non-Javadoc)
	 * @see org.eclipse.debug.ui.ILaunchConfigurationTab#performApply(org.eclipse.debug.core.ILaunchConfigurationWorkingCopy)
	 */
	public void performApply(ILaunchConfigurationWorkingCopy configuration) {
		
		String mode = new Boolean(fDefaultLog.getSelection()).toString();
		String sync = new Boolean(fSync.getSelection()).toString();
		
		String container = containerText.getText().trim();
		if (container.length() == 0) {
			container = null;
		}
		
		String model = modelText.getText().trim();
		if (model.length() == 0) {
			model = null;			
		}
		
		// get register module selected for transformation
		String moduleId = null;
		Transformation transformation = (Transformation) selectedModuleLabel.getData(MODULE_ID_KEY);
		if (transformation != null) {
			moduleId = transformation.getId();
		}

		String log = logText.getText().trim();
		if (log.length() == 0)
			log = null;
		
		configuration.setAttribute(LaunchConfigurationIds.DEFAULT_LOG_BOOL,mode);
		configuration.setAttribute(LaunchConfigurationIds.CONTAINER_ATTRIBUTE_ID, container);
		configuration.setAttribute(LaunchConfigurationIds.MODEL_ATTRIBUTE_ID, model);
		configuration.setAttribute(LaunchConfigurationIds.MODULE_ATTRIBUTE_ID, moduleId);
		configuration.setAttribute(LaunchConfigurationIds.LOG_ATTRIBUTE_ID, log);
		configuration.setAttribute(LaunchConfigurationIds.SYNC_BOOL, sync);
		configuration.setAttribute(LaunchConfigurationIds.PROPERTY_MAP_ID, propertyMap);
	}

	/*
	 * (non-Javadoc)
	 * @see org.eclipse.debug.ui.ILaunchConfigurationTab#getName()
	 */
	public String getName() {
		return "Code generation";
	}

	/*
	 * (non-Javadoc)
	 * @see org.eclipse.debug.ui.AbstractLaunchConfigurationTab#isValid(org.eclipse.debug.core.ILaunchConfiguration)
	 */
	public boolean isValid(ILaunchConfiguration launchConfig) {
		boolean valid = super.isValid(launchConfig)
		&& isModuleValid()
		&& isModelValid()
		&& isContainerValid()
		&& isLogValid();
		if (valid) {
			setMessage(null);
			setErrorMessage(null);
		}		
		return valid;
	}

	/**
	 * Check if the log file has been specified
	 * @return the validation boolean
	 */
	private boolean isLogValid() {
		if (logText.getText().length() == 0) {
			setMessage("Specify a log file");
			return false;
		}
		return true;
	}

	/**
	 * Check if the selected model exists
	 * @return the resource validation boolean
	 */
	private boolean isModelValid() {
		if (modelText.getText().length() > 0) {
			StringTokenizer st = new StringTokenizer(modelText.getText(), ":");
			while (st.hasMoreTokens()) {
				IPath path = new Path(st.nextToken().trim());			
				if (ResourcesPlugin.getWorkspace().getRoot().findMember(path) == null) {
					setErrorMessage("Specified model does not exist : " + path.toString());
					return false;
				} else {
					IResource containerResource = ResourcesPlugin.getWorkspace().getRoot().findMember(path);
					if (containerResource.getType() != IResource.FILE) {
						setErrorMessage("The model must be defined in a file.");
						return false;
					}	
				}
			}
		} else {
			setMessage("Specify a model");
			return false;
		}
		return true;
	}
	
	/**
	 * Check if the selected resource exists
	 * @return the resource validation boolean
	 */
	private boolean isContainerValid() {
		if (containerText.getText().length() > 0) {
			StringTokenizer st = new StringTokenizer(containerText.getText(), ":");
			while (st.hasMoreTokens()) {
				IPath path = new Path(st.nextToken().trim());			
				if (ResourcesPlugin.getWorkspace().getRoot().findMember(path) == null) {
					setErrorMessage("Specified container does not exist : " + path.toString());
					return false;
				}
			}
		} else {
			setMessage("Specify a container");
			return false;
		}
		return true;
	}

	/**
	 * Check if the selected module still exists
	 * @return the module validation boolean
	 */
	private boolean isModuleValid() {
				
		// get register module selected for transformation
		Transformation transformation = (Transformation) selectedModuleLabel.getData(MODULE_ID_KEY);
		if (transformation == null) {
			setMessage("Specify a module");
			return false;
		}
		return true;
	}
}
